#include "util.h"
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

/* structure to implement the queue */
struct item {
	char* url;
	int fd;
	int size;
	
	struct item *next;
	struct item *previous;
};

typedef struct item node;


/* structure to implement the cache */
struct cache {
	char* request;
	int fd;
	char* type;
	struct req *next;
};

typedef struct cache req;

int port;
char* path;
int num_dispatch;
int num_workers;
int num_prefetch;
int queue_len;
int mode;
int cache_entries;
static int queue_items;
node* head;
node* tail;

static pthread_mutex_t queueLock = PTHREAD_MUTEX_INITIALIZER; //lock for the queue
static pthread_mutex_t cacheLock = PTHREAD_MUTEX_INITIALIZER; //lock for the cache
static pthread_mutex_t fileLock = PTHREAD_MUTEX_INITIALIZER;  //lock for the file

static pthread_cond_t hasItems = PTHREAD_COND_INITIALIZER;  //conditional variable for queue
static pthread_cond_t noItems = PTHREAD_COND_INITIALIZER;

int fileEmpty = 1;
int fileLine = 0;

/*
 * inserts a request into the queue by order of FCFS
 */

void insertFCFS(int fd,char* buf)
{	
	
	printf("queue_items is: %d\n",queue_items);
	//create a new node and allocate memory for it
	node *newnode;
	newnode = (node*)malloc(sizeof(node));

	//initialize the url and fd fields of the new node
	newnode->url = buf;
	//printf("newnode->url got:%s\n",newnode->url);
	newnode->fd = fd;
	//printf("newnode->fd got:%d\n",newnode->fd);
	

	//if this is the only node in the list
	if(queue_items == 0)
	{
		head = NULL;
		tail = newnode;
		printf("node created\n");
	}
	else//if there is 1 or more nodes in the list
	{
		newnode->next = head;
		head->previous = newnode;
		head = newnode;
	}
	
}

void* insertSFF(int fd, char* buf)
{	//add an item to queue_items
	queue_items++;
	//create a new node and allocate memory for it
	node *newnode;
	newnode = (node*)malloc(sizeof(node));

	//initialize the url and fd fields of the new node
	newnode->url = buf;
	//printf("newnode->url got:%s\n",newnode->url);
	newnode->fd = fd;
	//printf("newnode->fd got:%d\n",newnode->fd);
	

	//if this is the only node in the list
	printf("queue_items is: %d\n",queue_items);
	if(queue_items == 1)
	{
		newnode->previous == NULL;
		newnode->next == NULL;
		head = newnode;
		tail = head;
		printf("node created\n");
	}
	else
	{
		node* tempnode;
		tempnode = tail;
		struct stat statOld;
		struct stat statNew;
		fstat(newnode->fd,&statNew);
		fstat(tail->fd,&statOld);
		printf("statNew contains:%d\nstatOld constains:%d\n",statNew.st_size,statOld.st_size);
		while(statNew.st_size > statOld.st_size)
		{
			tempnode = tempnode->previous;
		}
		newnode->next = tempnode->next;
		tempnode->next = newnode;
		newnode->previous = tempnode;
		if(tempnode->previous == NULL)
			head = newnode;
		else if(tempnode->next == NULL)
			tail = newnode;
	}
}

/* 
 * gets the next request from the queue in FCFS order
 */
node* getFCFS()
{
	if(queue_items == 0)
		return head;
	else
	{	//remove an item from the list
		
		printf("queue_items in get is: %d\n",queue_items);
		node *temp;
		temp = tail;
		tail = tail->previous;
		return temp;	
	}
}


/*
 * this function takes in the url argument of a request and determines its type
 */
char* determineType(char* element)
{
	if(element[1] == 't')
	{
		if(element[6] == 'h')
		{
			return "text/html";
		}
		else
			return "test/plain";
	}
	if(element[1] == 'i')
	{
		if(element[7] == 'g')
			return "image/gif";
		else
			return "image/jpeg";
	}
}
/*
 * worker thread function for the FCFS mode
 */
void * workerFCFS(void* arg)
{
	int i;
	for(i = 0; i<queue_len;i++)
	{
	 node* tempnode;
	 pthread_mutex_lock(&queueLock);//wait for lock
	 while(queue_items == 0)
		pthread_cond_wait(&hasItems,&queueLock);//wait for signal
	 tempnode = getFCFS();
	 queue_items--;
	 //printf("tempnode->url contains:%s\n",tempnode->url);
	 return_result(tempnode->fd,determineType(tempnode->url),tempnode->url,1024);
	/*we still need to implement a server log entry here*/
	 free(tempnode);
	 pthread_cond_signal(&noItems);
	 pthread_mutex_unlock(&queueLock);	
	}
}
	

void * dispatch(void* arg)
{	
	int fd;
	int i;
	char* buffer;
	buffer = (char*)malloc(sizeof(char)*1024);
	for(i = 0; i < queue_len; i++)
	{
		if((fd = accept_connection()) < 0)
			printf("accept connection failed\n");
		pthread_mutex_lock(&queueLock);
		while(queue_items == queue_len)
			pthread_cond_wait(&noItems,&queueLock);
		printf("before accept connection\n");
		
		printf("after accept connection\n");
		get_request(fd,buffer);
		insertFCFS(fd, buffer);
		queue_items++;
		printf("queue_items in dispatch is:%d\nand before signal\n",queue_items);
		pthread_cond_signal(&hasItems);
		printf("after signal in dispatch\n");
		pthread_mutex_unlock(&queueLock);
		printf("after the lock in dispatch\n");
	}
}

void * dispatchSFF()
{
	//initialize variables
	int fd = 1;
	int i =0;
	char* buffer;
	buffer = (char*)malloc(1024);

	//get requests and do a sorted insert into the queue
	while(fd > 0)
	{
		
		fd = accept_connection();
		get_request(fd,buffer);
		pthread_mutex_lock(&queueLock);//lock the queue
		insertSFF(fd,buffer);
		pthread_cond_signal(&hasItems);//signal a worker that there are items in the queue
		pthread_mutex_unlock(&queueLock);//unlock the queue
					
	}
	fileEmpty = -1;
}

void FCFS()
{	
	pthread_t *dispatcher;
	pthread_t *worker;
	dispatcher = (pthread_t*)malloc(sizeof(pthread_t)*num_dispatch);//number of dispatch threads
	worker = (pthread_t*)malloc(sizeof(pthread_t)*num_workers);//number of worker threads
	int i;
	
	for(i = 0; i < num_dispatch; i++)
	{
		pthread_create(&dispatcher[i],NULL,dispatch,NULL);//create dispatch threads
	}
	for(i = 0; i < num_workers; i++)
	{
		pthread_create(&worker[i],NULL,workerFCFS,NULL);//create worker threads
		
	}
	for(i = 0; i < num_workers;i++)
	{
		pthread_join(worker[i],NULL);
	}
	
	for(i = 0; i < num_dispatch; i++)
	{
		pthread_join(dispatcher[i],NULL);
	}	
	
}

void CRF()
{
	
}

void SFF()
{
	pthread_t *dispatcher;
	pthread_t *worker;
	dispatcher = (pthread_t*)malloc(sizeof(pthread_t)*num_dispatch);//number of dispatch threads
	worker = (pthread_t*)malloc(sizeof(pthread_t)*num_workers);//number of worker threads
	int i;
	for(i = 0; i < num_dispatch; i++)
	{
		pthread_create(&dispatcher[i],NULL,dispatchSFF,NULL);//create dispatch threads
	}
	for(i = 0; i < num_workers; i++)
	{
		pthread_create(&worker[i],NULL,workerFCFS,NULL);//create worker threads
		
	}
	for(i = 0; i < num_workers;i++)
	{
		pthread_join(worker[i],NULL);
	}
	for(i = 0; i < num_dispatch; i++)
	{
		pthread_join(dispatcher[i],NULL);
	}
}




int main (int argc, char **argv)
{
	
	port = atoi(argv[1]);
	path = argv[2];
	num_dispatch = atoi(argv[3]);
	num_workers = atoi(argv[4]);
	num_prefetch = atoi(argv[5]);
	queue_len = atoi(argv[6]);
	mode = atoi(argv[7]);
	cache_entries = atoi(argv[8]);
	queue_items = 0;
	
	if(argc != 9)
	{
		printf("Invalid number of arguments...Exiting\n");
		exit(0);
	}
	//printf("line 39\n");
	if(port < 1025 || port > 65535)
	{
		printf("Invalid port number...Exiting\n");
		exit(0);
	}
	//printf("line45\n");
	init(port);
	
	switch(mode)
	{
		case 0:
			FCFS();
			break;
		case 1:
			CRF();
			break;
		case 2:
			SFF();
			break;
		default:
			printf("Error, mode not correct...Exiting\n");
			exit(0);
	}
	return 0;
}

/* we are going to want to implement a queue structure to hold the requests, I'm not sure if we want to just 
   malloc space to void pointers so it is flexible or if we want to have different fields in order to hold
   more information */
